package utils.input;

import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.TreeSet;

import czolgisci.events.TickEvent;

import utils.delegate.MethodDelegate;
import utils.time.IClockListener;

/**
 * Klasa wykrywająca wciśnięcia klawiszy. Po otrzymaniu informacji o wcisnieciu
 * klawisza, obiekt tej klasy wywołuje konkretne metody konkretnych obiektów,
 * które wcześniej o to poprosiły.
 * 
 * Np. instancja klasy Tank rejestruje w obiekcie klasy KeyboardListener metodę
 * moveNorth pod klawiszem UP. Gdy gracz wcisnie klawisz UP, obiekt klasy
 * KeyboardListener wywoułej metodę instancji klasy Tank -> moveNorth().
 * 
 * @author kasztan
 * @version 1.2
 */
public class KeyboardListener implements KeyListener, IClockListener
{
	private HashMap<Character, MethodDelegate> keyContainer;
	private TreeSet<Character> pressedKeys;
	private TreeSet<Character> releasedKeys;
	
	/**
	 * 
	 * @param keyPressDispatcher
	 *            obiekt, z którego zostanie otrzymana informacja o wciśnięciu
	 *            klawiszy.
	 */
	public KeyboardListener(Component keyPressDispatcher)
	{
		keyPressDispatcher.addKeyListener(this);
		keyContainer = new HashMap<Character, MethodDelegate>();
		pressedKeys = new TreeSet<Character>();
		releasedKeys = new TreeSet<Character>();
	}

	/**
	 * Rejestruje metodę callback obiektu instance, którą należy wywołać po
	 * wciśnięciu klawisza key
	 * 
	 * @param key
	 *            klawisz klawiatury
	 * @param instance
	 *            obiekt, którego metoda ma być wywoływana po wciśnięciu
	 *            klawisza
	 * @param callback
	 *            metoda, która ma być wywołana
	 * @param arguments
	 *            argumenty metody callback
	 */
	public void registerKey(char key, MethodDelegate method)
	{
		keyContainer.put(new Character(key), method);
	}

	/**
	 * Usuwa z rejestru klawisz key
	 * 
	 * @param key
	 *            klawisz do usunięcia
	 */
	public void unregisterKey(char key)
	{
		keyContainer.remove(new Character(key));
	}

	/**
	 * Metoda wywoływana jeśli wciśnięto klawisz
	 * 
	 * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
	 */
	public void keyPressed(KeyEvent e)
	{
		Character c = getKeyCharacter(e);
		releasedKeys.remove(c);
		pressedKeys.add(c);
	}
	
	/**
	 * zwraca keyCode, jesli została wciśnięta strzałka, lub keyChar
	 * w przeciwnym wypadku.
	 * @param e
	 * @return Character (Code lub Char)
	 */
	private Character getKeyCharacter(KeyEvent e)
	{
		int code = e.getKeyCode();
		if(KeyEvent.VK_LEFT <= code && code <= KeyEvent.VK_DOWN || KeyEvent.VK_CONTROL == code)
			return new Character((char) code);
		else
			return new Character((char) e.getKeyChar());
	}
	/**
	 * Metoda wywoływana jeśli puszczono klawisz
	 * 
	 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
	 */
	public void keyReleased(KeyEvent e)
	{
		releasedKeys.add(getKeyCharacter(e));
	}

	/**
	 * Metoda wywoływana jeśli "wpisano" złożony znak Unicode.
	 * 
	 * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
	 */
	public void keyTyped(KeyEvent e)
	{
	}
	
	/**
	 * metoda wywoływana przez zegar
	 * @see utils.time.IClockListener#onTick(czolgisci.events.TickEvent)
	 */
	public void onTick(TickEvent e)
	{
		invokeAllPressedDelegates();
		removeAllReleasedKeys();
	}
	
	/**
	 * usuwa wszystkie klawisze, ktore zostaly "wycisniete"
	 */
	private void removeAllReleasedKeys()
	{
		for (Character ch : releasedKeys)
		{
			pressedKeys.remove(ch);
		}
		releasedKeys.clear();
	}

	/**
	 * metoda informuje wszystkie obiekty, których klawisze zostały wciśnięte
	 */
	private void invokeAllPressedDelegates()
	{
		for (Character pressedChar : pressedKeys)
		{
			invokeDelegate(pressedChar);
		}
	}

	/**
	 * metoda wywołuje zdarzenie odpowiedniego zarejestrowanego obiektu
	 * 
	 * @param c znak, ktorego obiekt ma zostać wywołany
	 */
	private void invokeDelegate(Character c)
	{
		if (keyContainer.containsKey(c))
		{
			try
			{
				keyContainer.get(c).invoke();
			} catch (InvocationTargetException e1)
			{
				System.err.println("MethodDelegate.invoke() failed: " + e1);
				// deleting MethodDelegate.invoke()
				keyContainer.remove(c);
				e1.printStackTrace();
			}
		}
	}
}
